using System;
using System.Collections.Generic;
using Godot;
using Godot.Collections;
using Array = Godot.Collections.Array;

public class LargeChunk
{
    public BlockWorld blockWorld;
    public const int ChunkSize = 256;
    public Vector2I Position;
    private BlockData[,,] blocks = new BlockData[ChunkSize, ChunkSize, 2];
    public List<Vector3I> Updatelist = new List<Vector3I>();
    public List<Vector3I> TickList = new List<Vector3I>();
    public List<Vector3I> RedStoneTickList = new List<Vector3I>();

    public LargeChunk(BlockWorld blockWorld, int X, int Y)
    {
        this.blockWorld = blockWorld;
        Position = new Vector2I(X, Y);
        for (int x_ = 0; x_ < ChunkSize; x_++)
            for (int y_ = 0; y_ < ChunkSize; y_++)
                for (int z_ = 0; z_ < 2; z_++)
                {
                    blocks[x_, y_, z_] = new BlockData
                    {
                        Name = "air",
                        chunk = this,
                        position = new Vector3I(x_, y_, z_),
                        GlobalPosition = new Vector3I(X * ChunkSize + x_, Y * ChunkSize + y_, z_)
                    };
                }
    }
    public void TickV2()
    {
        ResetPower();
        for (int i = 0; i < RedStoneTickList.Count; i++)
        {
            var pos = RedStoneTickList[i];
            BlockData block = blocks[pos.X, pos.Y, pos.Z];
            BlockMaterial blockMaterial = block.GetMaterial();
            if (block.blockEntity != null)
            {
                if (block.blockEntity.ticking <= 0)
                {
                    UpdataTickEvent ute = new UpdataTickEvent(blockWorld, pos)
                    {
                        block = block,
                    };
                    blockMaterial.RedstoneTick(ute);
                    block.blockEntity.ticking = block.blockEntity.tick;
                }
                else
                {
                    block.blockEntity.ticking--;
                }
            }
            else
            {
                UpdataTickEvent ute = new UpdataTickEvent(blockWorld, pos)
                {
                    block = block,
                };
                blockMaterial.RedstoneTick(ute);
            }

        }

        for (int i = 0; i < TickList.Count; i++)
        {
            var pos = TickList[i];
            BlockData block = blocks[pos.X, pos.Y, pos.Z];
            BlockMaterial blockMaterial = block.GetMaterial();
            if (block.blockEntity != null)
            {
                if (block.blockEntity.ticking <= 0)
                {
                    UpdataTickEvent ute = new UpdataTickEvent(blockWorld, pos)
                    {
                        block = block,
                    };
                    blockMaterial.TickEvent(ute);
                    if (block.blockEntity != null) block.blockEntity.ticking = block.blockEntity.tick;
                }
                else
                {
                    block.blockEntity.ticking--;
                }
            }
            else
            {
                UpdataTickEvent ute = new UpdataTickEvent(blockWorld, pos)
                {
                    block = block,
                };
                blockMaterial.TickEvent(ute);
            }
        }
    }
    public void Tick(TickType tickType)
    {
        for (int z = 0; z < 2; z++)
        {
            for (int x = 0; x < ChunkSize; x++)
            {
                for (int y = 0; y < ChunkSize; y++)
                {
                    BlockData block = blocks[x, y, z];
                    BlockMaterial blockMaterial = block.GetMaterial();
                    if (tickType == TickType.Normal && blockMaterial.TickEvent != null)
                    {
                        if (block.blockEntity != null)
                        {
                            if (block.blockEntity.ticking <= 0)
                            {
                                UpdataTickEvent ute = new UpdataTickEvent(blockWorld, new Vector3I(x, y, z))
                                {
                                    block = block,
                                };
                                blockMaterial.TickEvent(ute);
                                if (block.blockEntity != null) block.blockEntity.ticking = block.blockEntity.tick;
                            }
                            else
                            {
                                block.blockEntity.ticking--;
                            }
                        }
                        else
                        {
                            UpdataTickEvent ute = new UpdataTickEvent(blockWorld, new Vector3I(x, y, z))
                            {
                                block = block,
                            };
                            blockMaterial.TickEvent(ute);
                        }
                    }
                    else if (tickType == TickType.RedStone && blockMaterial.RedstoneTick != null)
                    {
                        if (block.blockEntity != null)
                        {
                            if (block.blockEntity.ticking <= 0)
                            {
                                UpdataTickEvent ute = new UpdataTickEvent(blockWorld, new Vector3I(x, y, z))
                                {
                                    block = block,
                                };
                                blockMaterial.RedstoneTick(ute);
                                block.blockEntity.ticking = block.blockEntity.tick;
                            }
                            else
                            {
                                block.blockEntity.ticking--;
                            }
                        }
                        else
                        {
                            UpdataTickEvent ute = new UpdataTickEvent(blockWorld, new Vector3I(x, y, z))
                            {
                                block = block,
                            };
                            blockMaterial.RedstoneTick(ute);
                        }
                    }
                }

            }
        }

    }
    // public void ResetTick()
    // {
    //     for (int x = 0; x < ChunkSize; x++)
    //         for (int y = 0; y < ChunkSize; y++)
    //             for (int z = 0; z < 2; z++)
    //                 blocks[x, y, z].IsTicked = false;

    // }
    public void ResetPower()
    {
        for (int x = 0; x < ChunkSize; x++)
            for (int y = 0; y < ChunkSize; y++)
                for (int z = 0; z < 2; z++)
                {
                    if (blocks[x, y, z].power != 0)
                    {
                        blocks[x, y, z].power = 0;
                        if (blocks[x, y, z].GetMaterial().UpdataBlockEvent != null)
                        {
                            blocks[x, y, z].GetMaterial().UpdataBlockEvent(new UpdataTickEvent(blockWorld, new Vector3I(x, y, z))
                            {
                                block = blocks[x, y, z]
                            });
                            UpdataBlockEventTick(new Vector3I(x, y, z));
                        }
                    }
                }

    }
    public void SetState(int x, int y, int z, int state)
    {
        this.blocks[x, y, z].State = state;
        Updatelist.Add(new Vector3I(x, y, z));
    }
    public BlockData SetBlock(Vector3I pos, BlockMaterial material) => SetBlock(pos.X, pos.Y, pos.Z, material);
    public BlockData SetBlock(int x, int y, int z, BlockData bd)
    {
        var position = new Vector3I(x, y, z);
        var bm = blocks[x, y, z].GetMaterial();
        var material = bd.GetMaterial();
        if (bm.RedstoneTick != null)
            for (int i = 0; i < RedStoneTickList.Count; i++)
                if (RedStoneTickList[i] == position)
                    RedStoneTickList.RemoveAt(i);

        if (bm.TickEvent != null)
            for (int i = 0; i < TickList.Count; i++)
                if (TickList[i] == position)
                    TickList.RemoveAt(i);

        if (material.TickEvent != null) TickList.Add(position);
        if (material.RedstoneTick != null) RedStoneTickList.Add(position);
        blocks[x, y, z] = bd;
        blocks[x, y, z].State = 0;
        blocks[x, y, z].chunk = this;
        blocks[x, y, z].position = new Vector3I(x, y, z);
        blocks[x, y, z].GlobalPosition = new Vector3I(Position.X * ChunkSize + x, Position.Y * ChunkSize + y);
        UpdataBlockEventTick(position);
        Updatelist.Add(position);
        return blocks[x, y, z];
    }

    public BlockData SetBlock(int x, int y, int z, BlockMaterial material)
    {
        if (x >= 0 && x < ChunkSize && y >= 0 && y < ChunkSize)
        {
            BlockData block = blocks[x, y, z];
            BlockMaterial bm = block.GetMaterial();
            if (bm.RedstoneTick != null)
                for (int i = 0; i < RedStoneTickList.Count; i++)
                    if (RedStoneTickList[i] == new Vector3I(x, y, z))
                        RedStoneTickList.RemoveAt(i);

            if (bm.TickEvent != null)
                for (int i = 0; i < TickList.Count; i++)
                    if (TickList[i] == new Vector3I(x, y, z))
                        TickList.RemoveAt(i);

            if (material.TickEvent != null) TickList.Add(new Vector3I(x, y, z));
            if (material.RedstoneTick != null) RedStoneTickList.Add(new Vector3I(x, y, z));
            if (material.CreateBlockEntity != null) block.blockEntity = material.CreateBlockEntity();
            else block.blockEntity = null;

            block.Name = material.Name;
            block.material = material;
            block.chunk = this;
            block.State = 0;
            block.Rotate = 0;

            block.position = new Vector3I(x, y, z);
            block.GlobalPosition = new Vector3I(Position.X * ChunkSize + x, Position.Y * ChunkSize + y);
            UpdataBlockEventTick(new Vector3I(x, y, z));
            Updatelist.Add(new Vector3I(x, y, z));
            return block;
        }
        return null;
    }
    public BlockData GetBlock(Vector3I v3) => GetBlock(v3.X, v3.Y, v3.Z);
    public BlockData GetBlock(int x, int y, int z = 0)
    {
        if (x >= 0 && x < ChunkSize && y >= 0 && y < ChunkSize)
            return blocks[x, y, z];
        return null;
    }
    public void UpdataBlockEventTick(Vector3I position)
    {
        var up = GetBlock(position - new Vector3I(0, 1));
        var buttom = GetBlock(position - new Vector3I(0, -1));
        var left = GetBlock(position - new Vector3I(-1, 0));
        var right = GetBlock(position - new Vector3I(1, 0));

        if (up != null && up.GetMaterial().UpdataBlockEvent != null)
            up.GetMaterial().UpdataBlockEvent(new UpdataTickEvent(blockWorld, position - new Vector3I(0, 1))
            {
                block = up
            });
        if (buttom != null && buttom.GetMaterial().UpdataBlockEvent != null)
            buttom.GetMaterial().UpdataBlockEvent(new UpdataTickEvent(blockWorld, position - new Vector3I(0, -1))
            {
                block = buttom
            });

        if (left != null && left.GetMaterial().UpdataBlockEvent != null)
            left.GetMaterial().UpdataBlockEvent(new UpdataTickEvent(blockWorld, position - new Vector3I(-1, 0))
            {
                block = left
            });
        if (right != null && right.GetMaterial().UpdataBlockEvent != null)
            right.GetMaterial().UpdataBlockEvent(new UpdataTickEvent(blockWorld, position - new Vector3I(1, 0))
            {
                block = right
            });
    }

    public int GetHight(int x, int z, bool PositiveOrder = true)
    {
        if (PositiveOrder)
        {
            for (int y = 0; y < ChunkSize; y++)
            {
                if (blocks[x, y, z].Name == "air")
                {
                    return y;
                }
            }
        }
        else
        {
            for (int y = ChunkSize - 1; y >= 0; y--)
            {
                if (blocks[x, y, z].Name == "air")
                {
                    return y;
                }
            }
        }
        return -1;

    }
    public void Spawn()
    {
        // Random rand = GetChunkRandom();
        OpenSimplexNoise Noise = new OpenSimplexNoise();
        // OpenSimplexNoise CaveNoise = new OpenSimplexNoise();
        // OpenSimplexNoise BiomeNoise = new OpenSimplexNoise();
        Noise.Seed = BlockWorld.Seed;
        // CaveNoise.Seed = World.Seed + 1;
        // BiomeNoise.Seed = World.Seed + 2;
        for (int z = 0; z < 2; z++)
            for (int x = 0; x < ChunkSize; x++)
            {
                // double r = rand.NextDouble();
                int X = Position.X * ChunkSize + x;
                int y = 64 + (int)(Noise.GetNoise2d((float)X, (float)z) * 32);
                for (int stone = 0; stone < y - 5; stone++)
                {
                    SetBlock(x, stone, z, Materials.Stone);
                }
                for (int stone = y - 5; stone < y; stone++)
                {
                    SetBlock(x, stone, z, Materials.Dirt);
                }
                SetBlock(x, y, z, Materials.Grass);
            }
    }
    public int GetSeed(int x, int y)
    {
        int ret = x;
        ret <<= 16;
        ret += y;
        return ret;
    }
    public Random GetChunkRandom() => new Random((int)GetSeed(Position.X, Position.Y));
    public bool Load()
    {
        File f = new File();
        if (!f.FileExists($"Save/{BlockWorld.WorldName}/{Position.X}_{Position.Y}.chunk"))
        {
            return false;
        }

        f.OpenCompressed($"Save/{BlockWorld.WorldName}/{Position.X}_{Position.Y}.chunk", File.ModeFlags.Read);
        Dictionary dict = (Dictionary)f.GetVar(true);
        f.Close();

        Position = new Vector2I((Dictionary)dict["position"]);
        //方块数据
        Array dblocks = (Array)dict["Blocks"];
        int index = 0;
        for (int x = 0; x < ChunkSize; x++)
            for (int y = 0; y < ChunkSize; y++)
                for (int z = 0; z < 2; z++)
                {
                    if (dblocks[index] != null)
                    {
                        BlockData blockData = new BlockData().Parse((Dictionary)dblocks[index]);
                        SetBlock(x, y, z, blockData);
                    }
                    index++;
                }

        GD.Print($"[加载]  Save/{BlockWorld.WorldName}/{Position.X}_{Position.Y}.chunk");
        return true;
    }
    public void Save()
    {
        Dictionary dict = new Dictionary();
        dict.Add("position", Position.GetDictionary());
        dict.Add("Blocks", BlocksToArray());
        Directory dir = new Directory();
        if (!dir.DirExists("Save"))
        {
            dir.MakeDir("Save");
        }
        if (!dir.DirExists($"Save/{BlockWorld.WorldName}/"))
        {
            dir.MakeDir($"Save/{BlockWorld.WorldName}/");
        }
        File f = new File();
        f.OpenCompressed($"Save/{BlockWorld.WorldName}/{Position.X}_{Position.Y}.chunk", File.ModeFlags.Write);
        f.StoreVar(dict, true);
        f.Close();
        GD.Print($"[保存]  Save/{BlockWorld.WorldName}/{Position.X}_{Position.Y}.chunk");
    }
    public Array BlocksToArray()
    {
        Array array = new Array();
        for (int x = 0; x < ChunkSize; x++)
            for (int y = 0; y < ChunkSize; y++)
                for (int z = 0; z < 2; z++)
                    array.Add(blocks[x, y, z].GetDictionary());
        return array;
    }

}